﻿/*! 
 * Implementation file for a collision heightfield.
 */
#include "PhxCollisionTerrain.h"

#include "PhxUtils.h"

using namespace phx;

const HeightFieldVertex& HeightField::GetVertex(const size_t iRow, const size_t iColumn) const
{
	return mElements.at(iRow * mColumns + iColumn);
}

HeightFieldVertex& HeightField::GetVertex(const size_t iRow, const size_t iColumn)
{
	return mElements.at(iRow * mColumns + iColumn);
}

const HeightFieldData& HeightField::GetVertexes() const
{
	return mElements;
}

HeightFieldData& HeightField::GetVertexes()
{
	return mElements;
}

const real HeightField::GetHeight(const size_t iRow, const size_t iColumn) const
{
	const HeightFieldVertex& vertex = mElements.at(iRow * mColumns + iColumn);
	return vertex.mVertexHeight;
}

const Material& HeightField::GetMaterial(const size_t iRow, const size_t iColumn) const
{
	const HeightFieldVertex& vertex = mElements.at(iRow * mColumns + iColumn);
	return vertex.mVertexMaterial;
}

Material& HeightField::GetMaterial(const size_t iRow, const size_t iColumn)
{
	HeightFieldVertex& vertex = mElements.at(iRow * mColumns + iColumn);
	return vertex.mVertexMaterial;
}

void HeightField::SetVertex(const size_t iRow, const size_t iColumn, const HeightFieldVertex& iVertex)
{
	mElements.at(iRow * mColumns + iColumn) = iVertex;
}

void HeightField::SetVertexes(const HeightFieldData& iVertexes)
{
	mElements = iVertexes;
}

void HeightField::SetHeight(const size_t iRow, const size_t iColumn, const real iHeight)
{
	HeightFieldVertex& vertex = mElements.at(iRow * mColumns + iColumn);
	vertex.mVertexHeight = iHeight;
}

void HeightField::SetMaterial(const size_t iRow, const size_t iColumn, const Material& iMaterial)
{
	HeightFieldVertex& vertex = mElements.at(iRow * mColumns + iColumn);
	vertex.mVertexMaterial = iMaterial;
}

size_t HeightField::GetRows() const
{
	return mRows;
}

size_t HeightField::GetColumns() const
{
	return mColumns;
}

CollisionTerrain::CollisionTerrain() 
	: mHorizontalScaleFactor(0)
	, mVerticalScaleFactor(0)
	, mModified(false) { }

CollisionTerrain::CollisionTerrain(const CollisionTerrainDescriptor& desc)
	: CollisionPrimitive(desc)
	, mHeights(desc.mHeights)
	, mHorizontalScaleFactor(desc.mHorizontalScaleFactor)
	, mVerticalScaleFactor(desc.mVerticalScaleFactor)
	, mModified(false) { }

void CollisionTerrain::SetHeightField(const HeightField& iHeightField)
{
	mHeights = iHeightField;
}

void CollisionTerrain::SetHeightFieldData(const size_t iRows, const size_t iColumns, const HeightFieldData& iHeightFieldData)
{
	mHeights = HeightField(iRows, iColumns, iHeightFieldData);
}

const HeightField& CollisionTerrain::GetHeightField() const
{
	return mHeights;
}

HeightField& CollisionTerrain::GetHeightField()
{
	return mHeights;
}

const HeightFieldData& CollisionTerrain::GetHeightFieldData() const
{
	return mHeights.GetVertexes();
}

HeightFieldData& CollisionTerrain::GetHeightFieldData()
{
	return mHeights.GetVertexes();
}

size_t CollisionTerrain::GetRows() const
{
	return mHeights.GetRows();
}

size_t CollisionTerrain::GetColumns() const
{
	return mHeights.GetColumns();
}

void CollisionTerrain::SetHorizontalScaleFactor(const real iHorizontalScale)
{
	mHorizontalScaleFactor = iHorizontalScale; 
}

const real CollisionTerrain::GetHorizontalScaleFactor() const
{
	return mHorizontalScaleFactor;
}

void CollisionTerrain::SetVerticalScaleFactor(const real iVerticalScale)
{
	mVerticalScaleFactor = iVerticalScale;
}

const real CollisionTerrain::GetVerticalScaleFactor() const
{
	return mVerticalScaleFactor;
}

void CollisionTerrain::SetScale(const real iHorizontalScale, const real iVerticalScale)
{
	mHorizontalScaleFactor = iHorizontalScale;
	mVerticalScaleFactor = iVerticalScale;
}

const real CollisionTerrain::GetHeight(const size_t iRow, const size_t iColumn) const
{
	return mHeights.GetHeight(iRow, iColumn) * mVerticalScaleFactor;
}

const Material& CollisionTerrain::GetMaterial(const size_t iRow, const size_t iColumn) const 
{
	return mHeights.GetMaterial(iRow, iColumn);
}

bool CollisionTerrain::HasBeenModified() const
{
	return mModified;
}

void CollisionTerrain::SetModificationStatus(const bool iStatus)
{
	mModified = iStatus;
}

int XWorldToMatrixTransformation(const real iX, const real iHalfWidth, const real iScale)
{
	int xMatrix = (int)((iX / iScale) + iHalfWidth);
	return xMatrix;
}

int YWorldToMatrixTransformation(const real iY, const real iHalfHeight, const real iScale)
{
	int yMatrix = (int)((-1 * (iY / iScale)) + iHalfHeight);
	return yMatrix;
}

void CollisionTerrain::ModifyTerrain(const Vector3& iExplosionPosition, const real iRay, const real iDepth)
{
	// TODO: Modifity a vertex only if it can be destroyed!

	int halfWidth	= PhxUtils::RoundReal(mHeights.GetColumns() * 0.5f);
	int halfHeight	= PhxUtils::RoundReal(mHeights.GetRows() * 0.5f);

	int column	= XWorldToMatrixTransformation(iExplosionPosition.x, (real)halfWidth, mHorizontalScaleFactor);
	int row		= YWorldToMatrixTransformation(iExplosionPosition.y, (real)halfHeight, mHorizontalScaleFactor);
	int ray		= PhxUtils::RoundReal(iRay / mHorizontalScaleFactor);

	real parabolaCoeff = (iDepth / (iRay * iRay));

	for (int i =-ray; i <= ray ; ++i)
	{
		for (int j =-ray; j <= ray ; ++j)
		{
			if ((row + i >= 0) && (row + i < (int)mHeights.GetRows()))
				if ((column + j >= 0) && (column + j < (int)mHeights.GetColumns()))
				{
					real currentDepth = mHeights.GetHeight(row + i, column + j);
					real heightToRemove = 0;
					if ((j == 0) || (i == 0))
					{
						real delta;
						if (j == 0)
							delta = (i * i) * parabolaCoeff;
						else 
							delta = (j * j) * parabolaCoeff;

						heightToRemove = iDepth - delta;
					}
					else
					{
						int squareSum = i * i + j * j;
						real hypotenuse = real_sqrt((real)squareSum);

						if (hypotenuse < iRay)
						{
							real delta = (hypotenuse * hypotenuse) * parabolaCoeff;
							heightToRemove = iDepth - delta;
						}
					}

					mHeights.SetHeight(row + i, column + j, currentDepth - (heightToRemove));
				}
		}
	}
}